package top.byteio.common.core.crypto;

import de.mkammerer.argon2.Argon2;
import de.mkammerer.argon2.Argon2Factory;
import lombok.Getter;

import java.nio.charset.StandardCharsets;

/** 
*
* Argon2d加密（适合密码哈希存储，抗GPU攻击）
* 核心功能：生成Argon2d哈希、验证密码与哈希匹配性
* @see <a href="https://github.com/phxql/argon2-jvm">Argon2-JVM</a>
* @author lerryxia
* @date 2025/10/16        
*/
public class Argon2d implements ICrypt{

    /**
     * Argon2d 默认参数（平衡安全性与性能，可根据场景调整）
     * <ul>
     *     <li>内存消耗：16384 KB (即 16 MB)</li>
     *     <li>迭代次数：2</li>
     *     <li>并行度：1</li>
     *     <li>版本：V13</li>
     *     <li>盐值长度：16字节</li>
     *     <li>哈希长度：32字节</li>
     * </ul>
     */
    private static final int DEFAULT_SALT_LENGTH = 16; //默认盐值长度

    private static final int DEFAULT_HASH_LENGTH = 32; //默认哈希长度

    private static final int DEFAULT_PARALLELISM = 1; // 并行度（CPU核心数）

    private static final int DEFAULT_MEMORY = 1 << 14; // 内存消耗（KB）

    private static final int DEFAULT_ITERATIONS = 2; // 迭代次数

    /**
     * 哈希长度
     */
    @Getter
    private final int hashLength;

    /**
     * 盐值长度
     */
    @Getter
    private final int saltLength;

    /**
     * 并行度（一般设置CPU核心数）
     */
    @Getter
    private final int parallelism;

    /**
     * 内存消耗（KB）
     */
    @Getter
    private final int memory;

    /**
     * 迭代次数
     */
    @Getter
    private final int iterations;

    public Argon2d(int hashLength, int saltLength, int parallelism, int memory, int iterations) {
        if (hashLength <= 0 || saltLength <= 0 || parallelism <= 0 || memory <= 0 || iterations <= 0) {
            throw new IllegalArgumentException("所有参数必须大于0");
        }
        this.hashLength = hashLength;
        this.saltLength = saltLength;
        this.parallelism = parallelism;
        this.memory = memory;
        this.iterations = iterations;
    }

    /**
     * 创建 Argon2d 默认实例
     * @return top.byteio.common.core.crypto.Argon2d 实例
     * @author lerryxia
     **/
    public static Argon2d createDefault() {
        return new Argon2d(
                DEFAULT_HASH_LENGTH,
                DEFAULT_SALT_LENGTH,
                DEFAULT_PARALLELISM,
                DEFAULT_MEMORY,
                DEFAULT_ITERATIONS);
    }

    /**
     * 创建 Argon2实例
     * @see <a href="https://github.com/phxql/argon2-jvm">Argon2-JVM</a>
     * @see Argon2
     * @return de.mkammerer.argon2.Argon2 实例
     * @author lerryxia
     **/
    private Argon2 getArgon2() {
        // Argon2Factory.Argon2Types.ARGON2d 指定算法为Argon2d
        return Argon2Factory.create(
                Argon2Factory.Argon2Types.ARGON2d,
                saltLength,
                hashLength
        );
    }

    /**
     * 生成 Argon2d 哈希（自动生成随机盐值，哈希结果包含算法参数）
     * @param plainPassword 明文密码（不可为null/空）
     * @return Argon2d 哈希字符串（格式：$argon2d$v=19$m=65536,t=3,p=4$盐值$哈希值）
     * @throws IllegalArgumentException 密码为null或空时抛出
     */
    public String encode(String plainPassword) {
        // 校验密码合法性
        if (plainPassword == null || plainPassword.trim().isEmpty()) {
            throw new IllegalArgumentException("明文密码不可为null或空");
        }

        // 转换密码为UTF-8字节数组（避免字符编码问题）
        byte[] passwordBytes = plainPassword.getBytes(StandardCharsets.UTF_8);

        Argon2 argon2 = getArgon2();
        try {
            // 使用当前对象的参数进行哈希计算
            return argon2.hash(iterations, memory, parallelism, passwordBytes);
        } finally {
            argon2.wipeArray(passwordBytes); // 清除敏感信息
        }
    }

    /**
     * 验证明文密码与 Argon2d 哈希是否匹配
     * @param plainPassword 明文密码（待验证）
     * @param argon2dHash 已存储的 Argon2d 哈希字符串
     * @return true：匹配；false：不匹配
     * @throws IllegalArgumentException 密码或哈希为null/空时抛出
     */
    public boolean verify(String plainPassword, String argon2dHash) {
        // 校验参数合法性
        if (plainPassword == null || plainPassword.trim().isEmpty()) {
            throw new IllegalArgumentException("明文密码不可为null或空");
        }
        if (argon2dHash == null || argon2dHash.trim().isEmpty()) {
            throw new IllegalArgumentException("Argon2d哈希不可为null或空");
        }

        // 转换密码为UTF-8字节数组
        byte[] passwordBytes = plainPassword.getBytes(StandardCharsets.UTF_8);
        Argon2 argon2 = getArgon2();

        try {
            // 验证哈希：参数（哈希字符串、密码字节）
            return argon2.verify(argon2dHash, passwordBytes);
        } finally {
            argon2.wipeArray(passwordBytes); // 清除敏感信息
        }
    }
}
